#include "xencode.h"
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/log.h>
#include <libavutil/opt.h>
}

static inline void PrintErr(int err) {
  static char buf[1024];
  memset(buf, 0, sizeof(buf));
  av_strerror(err, buf, sizeof(buf) - 1);
  av_log(nullptr, AV_LOG_ERROR, "%s\n", buf);
}

XEncode::XEncode() {}

AVCodecContext *XEncode::Create(int codec_id) {
  auto id = AVCodecID(codec_id);
  AVCodec *codec = avcodec_find_encoder(id);
  if (!codec) {
    av_log(nullptr, AV_LOG_INFO,
           "avcodec_find_encoder failed:无法查找到%d的编码器", codec_id);
    return nullptr;
  }
  AVCodecContext *c = avcodec_alloc_context3(codec);
  if (!c) {
    av_log(nullptr, AV_LOG_INFO,
           "avcodec_alloc_context3 failed:无法查找到%d的编码器", codec_id);
    return nullptr;
  }
  c->time_base = {1, 25};
  c->pix_fmt = AV_PIX_FMT_YUV420P;
  c->thread_count = 16;
  return c;
}

AVCodecContext *XEncode::Create(const char *name) {
  AVCodec *codec = avcodec_find_encoder_by_name(name);
  if (!codec) {
    av_log(nullptr, AV_LOG_INFO,
           "avcodec_find_encoder failed:无法查找到名为\"%s\"的编码器", name);
    return nullptr;
  }
  AVCodecContext *c = avcodec_alloc_context3(codec);
  if (!c) {
    av_log(nullptr, AV_LOG_INFO,
           "avcodec_alloc_context3 failed:无法查找到%s的编码器", name);
    return nullptr;
  }
  c->time_base = {1, 25};
  c->pix_fmt = AV_PIX_FMT_YUV420P;
  c->thread_count = 16;
  return c;
}

void XEncode::set_c(AVCodecContext *c) {
  std::unique_lock<std::mutex> lock(mux_);
  if (c_) {
    avcodec_free_context(&c_);
  }
  this->c_ = c;
}

bool XEncode::SepOpt(const char *key, const char *val) {
  std::unique_lock<std::mutex> lock(mux_);
  if (!c_)
    return false;
  auto re = av_opt_set(c_->priv_data, key, val, 0);
  if (re != 0) {
    PrintErr(re);
    return false;
  }
  return true;
}

bool XEncode::SepOpt(const char *key, int val) {
  std::unique_lock<std::mutex> lock(mux_);
  if (!c_)
    return false;
  auto re = av_opt_set_int(c_->priv_data, key, val, 0);
  if (re != 0) {
    PrintErr(re);
    return false;
  }
  return true;
}

bool XEncode::Open() {
  std::unique_lock<std::mutex> lock(mux_);
  if (!c_)
    return false;
  auto re = avcodec_open2(c_, nullptr, nullptr);
  if (re != 0) {
    PrintErr(re);
    return false;
  }
  return true;
}

AVPacket *XEncode::Encode(const AVFrame *frame, int *errNo) {
  if (errNo)
    *errNo = 0;
  if (!frame)
    return nullptr;
  std::unique_lock<std::mutex> lock(mux_);
  if (!c_)
    return nullptr;
  AVPacket *pkt = nullptr;
  pkt = av_packet_alloc();
  if (!pkt) {
    return nullptr;
  }
  auto re = avcodec_send_frame(c_, frame);
  if (re != 0) {
    if (errNo)
      *errNo = re;
    PrintErr(re);
    av_packet_free(&pkt);
    return nullptr;
  }
  re = avcodec_receive_packet(c_, pkt);
  if (re == 0) {
    return pkt;
  }
  av_packet_free(&pkt);
  if (errNo)
    *errNo = re;
  if (re == AVERROR(EAGAIN) || AVERROR_EOF) {
    return nullptr;
  }
  if (re < 0) {
    PrintErr(re);
    return nullptr;
  }
  return nullptr;
}
std::vector<AVPacket *> XEncode::End() {
  std::vector<AVPacket *> res;
  std::unique_lock<std::mutex> lock(mux_);
  if (!c_)
    return res;
  auto re = avcodec_send_frame(c_, nullptr);
  if (re != 0) {
    PrintErr(re);
    return res;
  }
  while (re >= 0) {
    AVPacket *pkt = nullptr;
    pkt = av_packet_alloc();
    re = avcodec_receive_packet(c_, pkt);
    if (re != 0) {
      av_packet_free(&pkt);
      break;
    }
    res.push_back(pkt);
  }
  return res;
}
AVFrame *XEncode::CreateFrame() {
  std::unique_lock<std::mutex> lock(mux_);
  if (!c_)
    return nullptr;
  auto frame = av_frame_alloc();
  frame->height = c_->height;
  frame->width = c_->width;
  frame->format = c_->pix_fmt;
  auto re = av_frame_get_buffer(frame, 0);
  if (re == 0) {
    return frame;
  }
  av_frame_free(&frame);
  if (re < 0) {
    PrintErr(re);
  }
  return nullptr;
}
